/*********************************************************************************************************************
 * COPYRIGHT NOTICE
 * Copyright (c) 2018,逐飞科技
 * All rights reserved.
 * 技术讨论QQ群：	一群：179029047(已满)
 *					二群：244861897(已满)
 *					三群：824575535
 *
 * 以下所有内容版权均属逐飞科技所有，未经允许不得用于商业用途，
 * 欢迎各位使用并传播本程序，修改内容时必须保留逐飞科技的版权声明。
 *
 * @file			seekfree_ali_mqtt.c
 * @company			成都逐飞科技有限公司
 * @author			逐飞科技(QQ3184284598)
 * @version			查看doc内version文件 版本说明
 * @Software		IAR 8.3 or MDK 5.24
 * @Taobao			https://seekfree.taobao.com/
 * @date			2020-03-25
 * @note			文件是针对 ALI 云编写的云服务器对接 MQTT 协议接口
 					基于 MQTT 3.1.1 以及 ESP8266 模块进行编写
 ********************************************************************************************************************/
#include "string.h"
#include "stdio.h"

#include "seekfree_ali_mqtt.h"
#include "seekfree_esp8266.h"
#include "utils_hmac.h"

#define ALI_IOT_SERVER_NAME				".iot-as-mqtt.cn-shanghai.aliyuncs.com"										// ALI 云服务器域名 这里是 华东2-上海
#define ALI_IOT_SERVER_PORT				"1883"																		// 服务器端口 1883
#define ALI_IOT_PING_S					100																			// 设备保活时间 超时不发送 PINGREQ 会导致设备掉线 数值默认 100 秒 请不要设置得特别大 最大只有 256

static aliyun_device_message_t aliyun_device;																		// 存放设备三元组以及所使用的 flexcomm

//-------------------------------------------------------------------------------------------------------------------
//	@brief		与服务器建立 TCP 连接并与对应设备完成对接
//	@param		message			设备三元组
//	@return		bool			是否成功与服务器建立 TCP 连接并对接到对应设备
//	Sample usage:				mqtt_aliyun_init(message);
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_aliyun_init (aliyun_device_message_t *message)
{
	aliyun_device.Index = message->Index;																			// 设置 flexcomm_index
	mqtt_set_productkey(message->productkey, message->productkey_len);												// 设置 productkey
	mqtt_set_devicename(message->devicename, message->devicename_len);												// 设置 devicename
	mqtt_set_devicesecre(message->devicesecre, message->devicesecre_len);											// 设置 devicesecre

	if(!mqtt_aliyun_tcp_init())																							// 初始化服务器连接参数 连接服务器
		return false;

	return true;
}

//-------------------------------------------------------------------------------------------------------------------
//	@brief		与服务器建立 TCP 连接并对接到对应设备
//	@return		bool			是否成功与服务器建立 TCP 连接并对接到对应设备
//	Sample usage:				mqtt_aliyun_tcp_init();
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_aliyun_tcp_init (void)
{

	char mqtt_server_ip[128];																						// 存放服务器IP或是域名

	memset(mqtt_server_ip, 0, 128);
	memcpy(mqtt_server_ip, aliyun_device.productkey, aliyun_device.productkey_len);									// 构建服务器域名
	strncat(mqtt_server_ip, ALI_IOT_SERVER_NAME, strlen(ALI_IOT_SERVER_NAME));										// 构建服务器域名

	if
	(
		!esp8266_great_tcp_connect																					// ESP8266 建立 TCP 连接接口
		(
			aliyun_device.Index,																					// flexcomm 口编号
			mqtt_server_ip,																							// 服务器域名
			aliyun_device.productkey_len+37,																		// 域名长度
			ALI_IOT_SERVER_PORT,																					// 端口
			strlen(ALI_IOT_SERVER_PORT)																				// 端口长度
		)
	)
		return false;																								// 与 ALI 云服务器建立连接失败 返回错误
	if(mqtt_connect_pack())																							// 调用 connect 接口
		return true;																								// 完成对接 返回
	return false;																									// 对接失败 返回错误
}

//-------------------------------------------------------------------------------------------------------------------
//	@brief		设备对接 CONNECT 包发送函数
//	@return		bool			是否成功对接到对应设备
//	Sample usage:				mqtt_connect_pack();
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_connect_pack (void)
{
	uint8_t			mqtt_fixed_len;																					// 固定报头长度
	uint8_t			mqtt_variable_len;																				// 可变报头长度
	uint8_t			mqtt_payload_len;																				// 有效负荷长度
	char			mqtt_temp_buff[300];																			// 临时缓冲区 构建报文用

	int				temp;

	char			temp_buffer[128];																				// 计算加密的时候 临时使用的缓冲区
	uint8_t			temp_buffer_len = 0;

	char			mqtt_client_id[128];																			// 存放客户端ID的缓冲区
	uint8_t			mqtt_client_id_len = 0;																			// 存放客户端ID的长度

	char			mqtt_username[128];																				// 存放用户名的缓冲区
	uint8_t			mqtt_username_len = 0;																			// 存放用户名的长度

	char			mqtt_password[128];																				// 存放密码的缓冲区
	uint8_t			mqtt_password_len = 0;																			// 存放密码的长度

	memset(mqtt_temp_buff, 0, 300);
	memset(temp_buffer, 0, 128);
	memset(mqtt_client_id, 0, 128);
	memset(mqtt_username, 0, 128);
	memset(mqtt_password, 0, 128);

	memset(mqtt_client_id, 0, 128);																					// 客户端ID的缓冲区全部清零
	memcpy(mqtt_client_id, aliyun_device.devicename, aliyun_device.devicename_len);									// 构建客户端ID 并存入缓冲区
	strncat(mqtt_client_id, "|securemode=3,signmethod=hmacsha1|", 34);												// 构建客户端ID 并存入缓冲区
	mqtt_client_id_len = aliyun_device.devicename_len + 34;															// 计算客户端ID的长度

	memset(mqtt_username, 0, 128);																					// 用户名的缓冲区全部清零
	memcpy(mqtt_username, aliyun_device.devicename, aliyun_device.devicename_len);									// 构建用户名 并存入缓冲区
	strncat(mqtt_username, "&", 1);																					// 构建用户名 并存入缓冲区
	strncat(mqtt_username, aliyun_device.productkey, aliyun_device.productkey_len);									// 构建用户名 并存入缓冲区
	mqtt_username_len = aliyun_device.devicename_len + aliyun_device.productkey_len + 1;							// 计算用户名的长度

	memset(temp_buffer, 0, 128);																					// 临时缓冲区全部清零
	memcpy(temp_buffer, "clientId", 8);																				// 构建加密时的明文
	strncat(temp_buffer, aliyun_device.devicename, aliyun_device.devicename_len);									// 构建加密时的明文
	strncat(temp_buffer, "deviceName", 10);																			// 构建加密时的明文
	strncat(temp_buffer, aliyun_device.devicename, aliyun_device.devicename_len);									// 构建加密时的明文
	strncat(temp_buffer, "productKey", 10);																			// 构建加密时的明文
	strncat(temp_buffer, aliyun_device.productkey, aliyun_device.productkey_len);									// 构建加密时的明文
	temp_buffer_len = aliyun_device.devicename_len * 2 + aliyun_device.productkey_len + 28;
	utils_hmac_sha1																									// 以DeviceSecret为秘钥对temp中的明文 进行hmacsha1加密 结果就是密码 并保存到缓冲区中
	(
		temp_buffer,
		temp_buffer_len,
		mqtt_password,
		aliyun_device.devicesecre,
		aliyun_device.devicesecre_len
	);
	mqtt_password_len = strlen(mqtt_password);																		// 计算用户名的长度

	memset(temp_buffer, 0, 128);

	mqtt_fixed_len = 1;																								// 连接报文中 固定报头长度暂时先=1
	mqtt_variable_len = 10;																							// 连接报文中 可变报头长度=10
	mqtt_payload_len = 6 + mqtt_client_id_len + mqtt_username_len + mqtt_password_len;								// 连接报文中 负载长度      
	temp_buffer_len = mqtt_variable_len + mqtt_payload_len;															// 剩余长度 = 可变报头长度 + 负载长度

	mqtt_temp_buff[0]=0x10;																							// 固定报头第1个字节	固定0x01		
	do{																												// 循环处理固定报头中的剩余长度字节 字节量根据剩余字节的真实长度变化
		temp = temp_buffer_len%128;																					// 剩余长度取余128
		temp_buffer_len = temp_buffer_len/128;																			// 剩余长度取整128
		if(temp_buffer_len>0)
			temp |= 0x80;																							// 按协议要求位7置位          
		mqtt_temp_buff[mqtt_fixed_len] = temp;																		// 剩余长度字节记录一个数据
		mqtt_fixed_len++;																							// 固定报头总长度+1    
	}while(temp_buffer_len>0);																						// 如果Remaining_len>0的话，再次进入循环

	temp_buffer_len = mqtt_fixed_len;
	mqtt_temp_buff[temp_buffer_len++]=0x00;																			//可变报头第 1个字节 	固定0x00
	mqtt_temp_buff[temp_buffer_len++]=0x04;																			//可变报头第 2个字节 	固定0x04
	mqtt_temp_buff[temp_buffer_len++]=0x4D;																			//可变报头第 3个字节 	固定0x4D M
	mqtt_temp_buff[temp_buffer_len++]=0x51;																			//可变报头第 4个字节 	固定0x51 Q
	mqtt_temp_buff[temp_buffer_len++]=0x54;																			//可变报头第 5个字节 	固定0x54 T
	mqtt_temp_buff[temp_buffer_len++]=0x54;																			//可变报头第 6个字节 	固定0x54 T
	mqtt_temp_buff[temp_buffer_len++]=0x04;																			//可变报头第 7个字节 	固定0x04
	mqtt_temp_buff[temp_buffer_len++]=0xC2;																			//可变报头第 8个字节 	使能用户名和密码校验，不使用遗嘱，不保留会话
	mqtt_temp_buff[temp_buffer_len++]=0x00;																			//可变报头第 9个字节 	保活时间高字节 0x00
	mqtt_temp_buff[temp_buffer_len++]=ALI_IOT_PING_S;																//可变报头第10个字节	保活时间高字节 0x64   100s

	// client id
	mqtt_temp_buff[temp_buffer_len++] = mqtt_client_id_len/256;														// 客户端ID长度高字节
	mqtt_temp_buff[temp_buffer_len++] = mqtt_client_id_len%256;														// 客户端ID长度低字节
	memcpy(&mqtt_temp_buff[temp_buffer_len], mqtt_client_id, mqtt_client_id_len);									// 复制过来客户端ID字串
	temp_buffer_len += mqtt_client_id_len;

	// username
	mqtt_temp_buff[temp_buffer_len++] = mqtt_username_len/256;														// 用户名长度高字节
	mqtt_temp_buff[temp_buffer_len++] = mqtt_username_len%256;														// 用户名长度低字节
	memcpy(&mqtt_temp_buff[temp_buffer_len], mqtt_username, mqtt_username_len);										// 复制过来用户名字串
	temp_buffer_len += mqtt_username_len;

	// password
	mqtt_temp_buff[temp_buffer_len++] = mqtt_password_len/256;														// 密码长度高字节
	mqtt_temp_buff[temp_buffer_len++] = mqtt_password_len%256;														// 密码长度低字节
	memcpy(&mqtt_temp_buff[temp_buffer_len], mqtt_password, mqtt_password_len);										// 复制过来密码字串
	temp_buffer_len += mqtt_password_len;

	if(0x01 != esp8266_send_message(aliyun_device.Index, mqtt_temp_buff, temp_buffer_len))							// 发送消息
		return false;																								// 发送失败 返回错误

	memset(temp_buffer, 0, 128);
	temp_buffer_len = 0;
	temp = 0;
	while(temp<0xff)
	{
		temp_buffer_len = esp8266_read_receive_buffer(temp_buffer);													// 从 ESP8266 缓冲区读取数据
		if(temp_buffer_len)																							// 读到数据了
		{
			for(; temp_buffer_len > 0; temp_buffer_len--)															// 轮询数据
			{
				if
				(
					(strncmp(&temp_buffer [temp_buffer_len], "+IPD,4:", 7) == 0)									// 是否是服务器发来的 4byte 数据
					&&	temp_buffer [temp_buffer_len+ 7] == 0x20													// 如果成功连接 服务器回复 0x20 0x02 0x00 0x00
					&&	temp_buffer [temp_buffer_len+ 8] == 0x02
					&&	temp_buffer [temp_buffer_len+ 9] == 0x00
					&&	temp_buffer [temp_buffer_len+10] == 0x00
				)
				{
					esp8266_clear_receive_buffer();																	// 清空缓冲区
					return true;																					// 返回链接成功
				}
			}
		}
		else
		{
			temp++;																									// 未接受到消息或消息不正确 超时自增
			if(temp == 0xff)																						// 超时
			{
				esp8266_clear_receive_buffer();																		// 清空buffer
				return false;																						// 返回错误
			}
		}
		delay_ms(1);
	}
	return false;																									// 不能运行到这一句 如果到这一句证明出错了
}

//-------------------------------------------------------------------------------------------------------------------
//	@brief		设备心跳 PINGREQ 包发送函数
//	@return		bool			PINGREQ是否成功
//	Sample usage:				mqtt_pingreq_pack();
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_pingreq_pack (void)
{
	char			temp_buffer[64];
	uint8_t			temp_buffer_len = 0;
	uint8_t			temp = 0;

	memset(temp_buffer, 0, 2);
	temp_buffer[0] = 0xC0;																							// 固定 0xC0
	temp_buffer[1] = 0x00;																							// 固定 0x00
	if(0x01 != esp8266_send_message(aliyun_device.Index, temp_buffer, 2))											// 发送消息包
		return false;																								// 发送失败 报错

	memset(temp_buffer, 0, 64);
	temp_buffer_len = 0;
	temp = 0;
	while(temp<0xff)
	{
		temp_buffer_len = esp8266_read_receive_buffer(temp_buffer);													// 读取 ESP8266 接收缓冲区
		if(temp_buffer_len)
		{
			for(; temp_buffer_len > 0; temp_buffer_len--)
			{
				if
				(
					(strncmp(&temp_buffer [temp_buffer_len], "+IPD,2:", 7) == 0)									// 接到服务器发送回来的消息
					&&	temp_buffer [temp_buffer_len+7] == 0xD0														// 0xD0 0x00 为成功
					&&	temp_buffer [temp_buffer_len+8] == 0x00
				)
				{
					esp8266_clear_receive_buffer();																	// 清空接收缓冲区
					return true;																					// 返回成功
				}
			}
		}
		else
		{
			temp++;																									// 未收到消息或者消息不正确
			if(temp == 0xff)																						// 超时
			{
				esp8266_clear_receive_buffer();																		// 清空接收区
				return false;																						// 返回失败
			}
		}
		delay_ms(1);
	}
	return false;																									// 不能运行到这一句 如果到这一句证明出错了
}

//-------------------------------------------------------------------------------------------------------------------
//	@brief		订阅 topic
//	@param		topic			目标 topic
//	@param		topic_len		topic 长度
//	@param		flag			报文标识符
//	@return		bool			订阅是否成功
//	Sample usage:				mqtt_subscribe_pack(&topic, 55, 0x01);
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_subscribe_pack (char *topic, uint8_t topic_len, uint8_t flag)
{
	char			temp_buffer[266];
	uint16_t		temp_buffer_len = topic_len + 5;
	uint16_t		temp = 0;

	memset(temp_buffer, 0, 256);
	temp_buffer[temp++] = 0x82;																						// 订阅 topic 包固定开头
	if(temp_buffer_len >= 0x80)																						// 长度超过 127
	{
		temp_buffer[temp++] = (uint8_t)(temp_buffer_len & 0x7F) | 0x80;												// 写长度低位
		temp_buffer[temp++] = 0x01;																					// 写长度高位
	}
	else
		temp_buffer[temp++] = temp_buffer_len;																		// 写长度
	temp_buffer[temp++] = 0x00;																						// 默认 0x00
	temp_buffer[temp++] = flag;																						// 报文标识符
	temp_buffer[temp++] = topic_len/256;																					// 有效载荷长度高位
	temp_buffer[temp++] = topic_len%256;																					// 有效载荷长度低位
	strncat(&temp_buffer[temp], topic, topic_len);																		// 写入目标 topic
	temp += topic_len;																									// 总长
	temp_buffer[temp++] = 0x00;																						// 写入等级0
	
	if(0x01 != esp8266_send_message(aliyun_device.Index, temp_buffer, temp))										// 发送消息
		return false;																								// 发送失败 返回错误

	memset(temp_buffer, 0, 128);
	temp_buffer_len = 0;
	temp = 0;
	while(temp<0xff)
	{
		temp_buffer_len = esp8266_read_receive_buffer(temp_buffer);													// 读取缓冲区
		if(temp_buffer_len)
		{
			for(; temp_buffer_len > 0; temp_buffer_len--)
			{
				if
				(
					(strncmp(&temp_buffer [temp_buffer_len], "+IPD,5:", 7) == 0)									// 确认返回的消息
					&&	temp_buffer [temp_buffer_len+ 7] == 0x90													// 如果正确 返回的应该是这几个数据
					&&	temp_buffer [temp_buffer_len+ 8] == 0x03
					&&	temp_buffer [temp_buffer_len+ 9] == 0x00
					&&	temp_buffer [temp_buffer_len+10] == flag
					&&	temp_buffer [temp_buffer_len+11] == 0x01
				)
				{
					esp8266_clear_receive_buffer();																	// 清空数据缓冲区
					return true;																					// 返回成功
				}
			}
		}
		else
		{
			temp++;																									// 无数据或者数据不正确
			if(temp == 0xff)																						// 超时
			{
				esp8266_clear_receive_buffer();																		// 清空缓冲区
				return false;																						// 返回错误
			}
		}
		delay_ms(1);
	}
	return false;																									// 不能运行到这一句 如果到这一句证明出错了
}

//-------------------------------------------------------------------------------------------------------------------
//	@brief		取消订阅 topic
//	@param		topic			目标 topic
//	@param		topic_len		topic 长度
//	@param		flag			报文标识符
//	@return		bool			取消是否成功
//	Sample usage:				mqtt_unsubscribe_pack(&topic, 55, 0x01);
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_unsubscribe_pack (char *topic, uint8_t topic_len, uint8_t flag)
{
	char			temp_buffer[266];
	uint16_t		temp_buffer_len = topic_len + 4;
	uint16_t		temp = 0;

	memset(temp_buffer, 0, 256);
	temp_buffer[temp++] = 0xA2;																						// 取消订阅包头固定字节
	if(temp_buffer_len >= 0x80)																						// 长度超过 127
	{
		temp_buffer[temp++] = (uint8_t)(temp_buffer_len & 0x7F) | 0x80;												// 写长度低位
		temp_buffer[temp++] = 0x01;																					// 写长度高位
	}
	else
		temp_buffer[temp++] = temp_buffer_len;																		// 写长度
	temp_buffer[temp++] = 0x00;																						// 标识符 0x00
	temp_buffer[temp++] = flag;																						// 标识符
	temp_buffer[temp++] = topic_len/256;																					// topic 长度高位
	temp_buffer[temp++] = topic_len%256;																					// topic 长度低位
	strncat(&temp_buffer[temp], topic, topic_len);																		// 写入 topic
	temp += topic_len;																									// 计算长度
	
	if(0x01 != esp8266_send_message(aliyun_device.Index, temp_buffer, temp))										// 发送消息
		return false;																								// 发送失败 返回错误

	memset(temp_buffer, 0, 128);
	temp_buffer_len = 0;
	temp = 0;
	while(temp<0xff)
	{
		temp_buffer_len = esp8266_read_receive_buffer(temp_buffer);													// 从 ESP8266 接收缓冲读取数据
		if(temp_buffer_len)
		{
			for(; temp_buffer_len > 0; temp_buffer_len--)
			{
				if
				(
					(strncmp(&temp_buffer [temp_buffer_len], "+IPD,4:", 7) == 0)									// 接到服务器消息
					&&	temp_buffer [temp_buffer_len+ 7] == 0xB0													// 退订的固定回复
					&&	temp_buffer [temp_buffer_len+ 8] == 0x02
					&&	temp_buffer [temp_buffer_len+ 9] == 0x00
					&&	temp_buffer [temp_buffer_len+10] == flag
				)
				{
					esp8266_clear_receive_buffer();																	// 清空接收缓冲
					return true;																					// 返回成功
				}
			}
		}
		else
		{
			temp++;																									// 无消息或消息不正确
			if(temp == 0xff)																						// 超时
			{
				esp8266_clear_receive_buffer();																		// 清空接收区
				return false;																						// 返回错误
			}
		}
		delay_ms(1);
	}
	return false;																									// 不能运行到这一句 如果到这一句证明出错了
}

//-------------------------------------------------------------------------------------------------------------------
//	@brief		推送消息
//	@param		topic			目标 topic
//	@param		topic_len		topic 长度
//	@param		message			json 消息 这个需要自行根据设备进行解析
//	@param		message_len		消息长度
//	@return		bool			推送是否成功
//	Sample usage:				mqtt_publish_pack(&topic, topic_len, &message, message_len);
//-------------------------------------------------------------------------------------------------------------------
bool mqtt_publish_pack (char *topic, uint8_t topic_len, char *message, uint16_t message_len)
{
	char			temp_buffer[522];
	uint16_t		temp_buffer_len = topic_len + message_len + 2;
	uint16_t		temp = 0;

	memset(temp_buffer, 0, 522);
	temp_buffer[temp++] = 0x30;																						// 推送消息固定包头
	if(temp_buffer_len >= 0x4000)																					// 长度……超过过了16383
	{
		temp_buffer[temp++] = (uint8_t)(temp_buffer_len & 0x7F) | 0x80;												// 写长度低位
		temp_buffer[temp++] = (uint8_t)((temp_buffer_len >> 7) & 0x7F) | 0x80;										// 写长度中位
		temp_buffer[temp++] = (uint8_t)((temp_buffer_len >> 14) & 0x7F);											// 写长度高位
	}
	else if(temp_buffer_len >= 0x80)
	{
		temp_buffer[temp++] = (uint8_t)(temp_buffer_len & 0x7F) | 0x80;												// 写长度低位
		temp_buffer[temp++] = 0x01;																					// 写长度高位
	}
	else
		temp_buffer[temp++] = temp_buffer_len;																		// 写长度

	temp_buffer[temp++] = topic_len/256;																			// 写目标 topic 长度高位
	temp_buffer[temp++] = topic_len%256;																			// 写目标 topic 长度低位
	strncat(&temp_buffer[temp], topic, topic_len);																	// 写目标 topic
	temp += topic_len;																								// 计算长度
	strncat(&temp_buffer[temp], message, message_len);																// 写消息
	temp += message_len;																							// 计算长度
	
	if(0x01 != esp8266_send_message(aliyun_device.Index, temp_buffer, temp))										// 发送消息包
		return false;																								// 发送失败 返回错误

	return true;																									// 推送成功
}

void mqtt_set_productkey (char *productkey, uint8_t len)
{
	memset(aliyun_device.productkey, 0, 32);
	memcpy(aliyun_device.productkey, productkey, len);
	aliyun_device.productkey_len = len;
}

void mqtt_set_devicename (char *devicename, uint8_t len)
{
	memset(aliyun_device.devicename, 0, 32);
	memcpy(aliyun_device.devicename, devicename, len);
	aliyun_device.devicename_len = len;
}

void mqtt_set_devicesecre (char *devicesecre, uint8_t len)
{
	memset(aliyun_device.devicesecre, 0, 64);
	memcpy(aliyun_device.devicesecre, devicesecre, len);
	aliyun_device.devicesecre_len = len;
}
